{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "ae1b9e3a",
   "metadata": {},
   "source": [
    "# Settings Attribute Machinery\n",
    "\n",
    "Each object has a `settings` attribute which is used to store relevant and useful data. This is particularly useful for the algorithms where things like solver tolerance can be set.  This notebook will given an overview of this attribute and its features and behavior."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "275b8a14",
   "metadata": {},
   "outputs": [],
   "source": [
    "import openpnm as op"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d7d20e27",
   "metadata": {},
   "source": [
    "Let's start by creating a `StokesFlow` algorithm, which has plenty of settings so is good to demonstrate:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "4cb9c32f",
   "metadata": {},
   "outputs": [],
   "source": [
    "pn = op.network.Demo(shape=[4, 4, 1])\n",
    "w = op.phase.Water(network=pn)\n",
    "w.add_model_collection(op.models.collections.physics.basic)\n",
    "flow = op.algorithms.StokesFlow(network=pn, phase=w)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9d1b9684",
   "metadata": {},
   "source": [
    "First lets print the `settings` attribute:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "455a5f6b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n",
      "Settings                            Values\n",
      "――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n",
      "uuid                                f113b2df-517e-47fc-9907-8ee4e9262353\n",
      "default_domain                      domain_1\n",
      "cache                               True\n",
      "conductance                         throat.hydraulic_conductance\n",
      "phase                               phase_01\n",
      "quantity                            pore.pressure\n",
      "variable_props                      TypedSet()\n",
      "f_rtol                              1e-06\n",
      "newton_maxiter                      5000\n",
      "relaxation_factor                   1.0\n",
      "x_rtol                              1e-06\n",
      "――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n"
     ]
    }
   ],
   "source": [
    "print(flow.settings)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "db60d0e1",
   "metadata": {},
   "source": [
    "`settings` is a custom class that behaves somewhat like Python's `dataclass` but is actually useful (sorry Python). Let's start by exploring the settings on `flow` (without talking about their meaning)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e14ecaf7",
   "metadata": {},
   "source": [
    "## Datatype is enforced\n",
    "Once a data type is written to an attribute, all subsequent values must be of the same type. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "ae3008df",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2.0\n"
     ]
    }
   ],
   "source": [
    "flow.settings.f_rtol = 2.0\n",
    "print(flow.settings.f_rtol)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "fbddb4ba",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Attribute 'f_rtol' can only accept values of type <class 'float'>, but the recieved value was of type <class 'str'>\n"
     ]
    }
   ],
   "source": [
    "try:\n",
    "    flow.settings.f_rtol = 'two'\n",
    "except Exception as e:\n",
    "    print(e)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "62420c93",
   "metadata": {},
   "source": [
    "## Settings can be access as attributes or dict keys\n",
    "\n",
    "This feature just makes it easy to access things programmatically using `flow.settings[key]` rather then `getattr(flow.settings, key)`, while ensuring the `settings` attributes can still be easily viewed using tab-completion."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "8fee5e96",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2.0\n",
      "3.0\n"
     ]
    }
   ],
   "source": [
    "print(flow.settings['f_rtol'])\n",
    "flow.settings['f_rtol'] = 3.0\n",
    "print(flow.settings.f_rtol)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "48c61be0",
   "metadata": {},
   "source": [
    "## Namespace is clean\n",
    "\n",
    "When developing this feature we considered *many* relevant packages like `traits`, `attrs`, `pydantic`, etc, but they all had a major drawback: The namespace the class was cluttered with many methods relevant to using the package.  We wanted the `settings` attribute to contain *only* settings:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "1b2cbc86",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "cache\n",
      "conductance\n",
      "default_domain\n",
      "f_rtol\n",
      "newton_maxiter\n",
      "phase\n",
      "quantity\n",
      "relaxation_factor\n",
      "uuid\n",
      "variable_props\n",
      "x_rtol\n"
     ]
    }
   ],
   "source": [
    "for item in dir(flow.settings):\n",
    "    if not item.startswith('_'):\n",
    "        print(item)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "64ab930a",
   "metadata": {},
   "source": [
    "## Collections also enforce types\n",
    "\n",
    "As shown above, once a setting is given a certain value, all future values must have the same datatype. We also implemented several \"typed\" collections, like `TypedList` and `TypedSet`, which insist that all elements must have the same type:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "fd97a84b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "TypedSet()\n"
     ]
    }
   ],
   "source": [
    "print(flow.settings.variable_props)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "58e50219",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "TypedSet({'pore.pressure'})\n"
     ]
    }
   ],
   "source": [
    "flow.settings.variable_props.add('pore.pressure')\n",
    "print(flow.settings.variable_props)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "3fd9c668",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "This list cannot accept values of type <class 'float'>\n"
     ]
    }
   ],
   "source": [
    "try:\n",
    "    flow.settings.variable_props.add(0.0)\n",
    "except Exception as e:\n",
    "    print(e)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6c159a00",
   "metadata": {},
   "source": [
    "Note that the first entry into the `TypedSet` defines the type of all subsequent entries. The reason for this type enforcement is basically to prevent users from writing a value to a setting that OpenPNM does not expect. OpenPNM fetches these values from various places in the code and uses them, so they must be *usable*."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "61738034",
   "metadata": {},
   "source": [
    "## Settings are attached before init\n",
    "\n",
    "An instance of the `Settings` class is attached to the `settings` attribute of every object even prior to initialization.  This is done by overloading the `__new__` method of the `Base2` class, from which every OpenPNM object descends.  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "8a7941f8",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n",
      "Settings                            Values\n",
      "――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n",
      "uuid                                7135073a-f29a-4c94-9311-041acd9e036d\n",
      "default_domain                      domain_1\n",
      "――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n"
     ]
    }
   ],
   "source": [
    "new = op.core.Base2()\n",
    "print(new.settings)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5a4754fa",
   "metadata": {},
   "source": [
    "During this step the `uuid` is generated, to ensure all objects have a unique value here (This is useful if an object is saved and reloaded for instance). "
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
